<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage grove
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

interface HttpRequest {

    const METHOD_HEAD	= 1;
    const METHOD_GET	= 2;
    const METHOD_POST	= 3;
    const METHOD_PUT	= 4;
    const METHOD_DELETE	= 5;
    const METHOD_TRACE	= 6;

    public function getCookie($name);
    public function getCookies();

    public function getHeader($name);
    public function getHeaders();

    public function getPort();

    public function getMethod();

    public function getRemoteAddress();
    public function getRemoteHost();

    public function isSecure();

    public function getDomain();
    public function getUri();
    public function getFilePathName();
    public function getQueryString();

    public function isRewritten();

    public function getScriptName();
    public function getPathInfo();

    public function getUserAgent();
    public function getReferrer();

    public function getPostParameter($name);
    public function getPostParameters();


    /**
     * Return an array of all uploaded files (or uploaded files nested in this array, by name)
     *
     * @return array
     */
    public function getFiles();

    public function getQueryParameter($name);
    public function getQueryParameters();

    public function getLocale();
    public function getLocales();

}

abstract class UploadedFile implements SplFileObject {

    const STATUS_OK					= \UPLOAD_ERR_OK;

    const STATUS_EXCEEDS_INI_SIZE	= \UPLOAD_ERR_INI_SIZE;
    const STATUS_EXCEEDS_FORM_SIZE	= \UPLOAD_ERR_FORM_SIZE;

    const STATUS_PARTIALY_UPLOADED	= \UPLOAD_ERR_PARTIAL;
    const STATUS_NOT_UPLOADED		= \UPLOAD_ERR_NO_FILE;

    const STATUS_NO_TMP_FOLDER		= \UPLOAD_ERR_NO_TMP_DIR;
    const STATUS_WRITE_FAILED		= \UPLOAD_ERR_CANT_WRITE;

    const STATUS_INVALID_EXTENSION	= \UPLOAD_ERR_EXTENSION;


    /**
     *
     * @return string
     */
    public abstract function getOriginalFilename();

    /**
     *
     * @return int
     */
    public abstract function getStatus();

    /**
     *
     * @return boolean
     */
    public function isSuccessfullyUploaded() {
        return $this->getStatus() === UploadedFile :: STATUS_OK;
    }

    /**
     *
     * @param string $name
     * @return \mg\UploadedFile
     */
    public static function resolveInstance($name = null) {
        if($name === null) {
            return null;
        }

        return in(function(HttpRequest $httpRequest) use($name) {
            $files = $httpRequest->getFiles();

            if(isset($files[$name]) && ($files[$name] instanceof UploadedFile)) {
                return $files[$name];
            }

            return null;
        });
    }
}


/**
 *
 * @author Michiel Hakvoort
 * @deprecated
 *
 */
abstract class UploadedFileSet {

    public abstract function getName();

    public abstract function getFiles();
    public abstract function getFile($name);

    public abstract function getFileSets();
    public abstract function getFileSet($name);

    /**
     *
     * @param string $name
     * @return UploadedFileSet
     */
    public static function resolveInstance($name = null) {
        if($name === null) {
            return null;
        }

        return in(function(HttpRequest $httpRequest) use($name) {
            return $httpRequest->getFileSet($name);
        });
    }
}

class SimulatedHttpRequest implements HttpRequest {

    public $domain = null;
    public $pathInfo = null;
    public $scriptName = null;
    public $isRewritten =  null;
    public $port = null;

    public function getCookie($name) {
        return null;
    }

    public function getCookies() {
        return array();
    }

    public function getHeader($name) {
        return null;
    }

    public function getHeaders() {
        return array();
    }

    public function getPort() {
        return $this->port;
    }

    public function getMethod() {
        return self :: METHOD_GET;
    }

    public function getRemoteAddress() {
        return '127.0.0.1';
    }

    public function getRemoteHost() {
        return 'localhost';
    }

    public function isSecure() {
        return false;
    }

    public function getDomain() {
        return $this->domain;
    }

    public function getUri() {
        return '/';
    }

    public function getFilePathName() {
        return '/';
    }

    public function getQueryString() {
        return '';
    }

    public function isRewritten() {
        return $this->isRewritten;
    }

    public function getScriptName() {
        return $this->scriptName;
    }

    public function getPathInfo() {
        return $this->pathInfo;
    }

    public function getUserAgent() {
        return '';
    }

    public function getReferrer() {
        return '';
    }

    public function getPostParameter($name) {
        return null;
    }

    public function getPostParameters() {
        return array();
    }

    /**
     * @return UploadedFile
     *
     * @param unknown_type $name
     * @return unknown_type
     */
    public function getFile($name) {
        return null;
    }

    public function getFiles() {
        return array();
    }

    public function getFileSet($name) {
        return null;
    }

    public function getFileSets() {
        return array();
    }

    public function getQueryParameter($name) {
        return null;
    }

    public function getQueryParameters() {
        return array();
    }

    public function getLocale() {
        return Locale :: getLocale('en_US');
    }

    public function getLocales() {
        return array($this->getLocale());
    }

}

class DefaultHttpRequest implements HttpRequest {

    private static $queryParameters = null;
    private static $postParameters = null;
    private static $cookies = null;
    private static $acceptLanguages = null;

    private static $isGlobalInitialized = false;

    private $files = null;

    private $headers = null;
    private $locale = null;
    private $locales = null;

    public function __construct() {
        $this->locale = false;

        $this->init();
    }

    public function getCookie($name) {
        return isset(self :: $cookies[$name]) ? self :: $cookies[$name] : null;
    }

    public function getCookies() {
        return self :: $cookies;
    }

    public function getHeader($name) {
        $headers = $this->getHeaders();
        return isset($headers[$name]) ? $headers[$name] : null;
    }

    public function getHeaders() {
        if($this->headers !== null) {
            return $this->headers;
        }

        $headers = array();
        foreach($_SERVER as $key => $value) {
            $key = mb_strtolower($key);
            if(mb_substr($key, 0, 5) === 'http_') {
                if(mb_strlen($key) === 5) {
                    continue;
                }

                $header = mb_substr($key, 5);

                $header{0} = mb_strtoupper($header{0});
                $pos = 0;

                while(($pos = mb_strpos($header, '_', $pos)) !== false) {
                    $header{$pos} = '-';
                    $pos++;

                    if($pos > mb_strlen($header)) {
                        continue;
                    }

                    $header{$pos} = mb_strtoupper($header{$pos});
                }
                $headers[$header] = $value;
            }
        }

        $this->headers = $headers;
        return $headers;
    }

    public function getPort() {
        return isset($_SERVER['SERVER_PORT']) ? intval($_SERVER['SERVER_PORT']) : 80;
    }

    public function getMethod() {
        switch(mb_strtoupper($_SERVER['REQUEST_METHOD'])) {
            case 'HEAD'   : return self :: METHOD_HEAD;
            case 'GET'    : return self :: METHOD_GET;
            case 'POST'   : return self :: METHOD_POST;
            case 'PUT'    : return self :: METHOD_PUT;
            case 'DELETE' : return self :: METHOD_DELETE;
            case 'TRACE'  : return self :: METHOD_TRACE;

            default       : return self :: METHOD_GET;
        }
    }

    public function getRemoteAddress() {
        return $_SERVER['REMOTE_ADDR'];
    }

    public function getRemoteHost() {
        return $_SERVER['REMOTE_HOST'];
    }

    public function isSecure() {
        return isset($_SERVER['HTTPS']);
    }

    public function isRewritten() {
        return isset($_SERVER['REDIRECT_URI']) || isset($_SERVER['REDIRECT_URL']);
    }

    public function getDomain() {
        $domain = $_SERVER['HTTP_HOST'];

        if(mb_stripos($domain, ':') !== false) {
            list($domain, $_) = explode(':', $domain, 2);
        }

        return $domain;
    }

    public function getUri() {
        return $_SERVER['REQUEST_URI'];
    }

    public function getFilePathName() {
        $filePathName = $this->getUri();

        if(($queryPos = mb_strpos($filePathName, '?')) !== false) {
            $filePathName = mb_substr($filePathName, 0, $queryPos);
        }

        return $filePathName;
    }

    public function getQueryString() {
        return $_SERVER['QUERY_STRING'];
    }

    public function getScriptName() {
        return $_SERVER['SCRIPT_NAME'];
    }

    public function getPathInfo() {
        return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
    }

    public function getUserAgent() {
        return $_SERVER['HTTP_USER_AGENT'];
    }

    public function getReferrer() {
        return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
    }

    public function getPostParameter($name) {
        return isset(self :: $postParameters[$name]) ? self :: $postParameters[$name] : null;
    }

    public function getPostParameters() {
        return self :: $postParameters;
    }

    public function getQueryParameter($name) {
        return isset(self :: $queryParameters[$name]) ? self :: $queryParameters[$name] : null;
    }

    public function getQueryParameters() {
        return self :: $queryParameters;
    }

    public function getFiles() {
        return $this->files;
    }

    public function getLocale() {
        if($this->locale !== false) {
            return $this->locale;
        }

        $locale = null;

        if(!empty(self :: $acceptLanguages)) {
            reset(self :: $acceptLanguages);

            while($locale === null && ($language = key(self :: $acceptLanguages)) !== null) {
                $locale = Locale :: getLocale(key(self :: $acceptLanguages));
                next(self :: $acceptLanguages);
            }

        }

        $this->locale = $locale;

        return $locale;
    }

    public function getLocales() {
        if($this->locales !== null) {
            return $this->locales;
        }

        $locales = array();

        foreach(self :: $acceptLanguages as $acceptLanguage => $weight) {
            $locale = Locale :: getLocale($acceptLanguage);
            if($locale !== null) {
                $locales[] = $locale;
            }
        }

        $this->locales = $locales;


        return $locales;
    }

    /**
     * The static preparation for the DefaultHttpRequest involves
     * stripping magic quotes from $_GET, $_POST and $_COOKIE and
     * the removal of null-bytes from the user input.
     *
     * Next to cleaning input, accepted Locales are parsed from the request
     * and uploaded files are collected and stored into UploadedFiles and
     * UploadedFileSets.
     *
     */
    private function init() {

        if(!self :: $isGlobalInitialized) {
            // Sanitize input
            if(get_magic_quotes_gpc()) {
                self :: unescape($_GET);
                self :: unescape($_POST);
                self :: unescape($_COOKIE);
            }

            self :: stripNullBytes($_GET);
            self :: stripNullBytes($_POST);
            self :: stripNullBytes($_COOKIE);

            self :: $queryParameters = $_GET;
            self :: $postParameters = $_POST;
            self :: $cookies = $_COOKIE;

            // Prepare accepted languages, TODO investigate possible security flaws
            if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
                $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

                $acceptLanguages = array();
                foreach($languages as $language) {
                    $quality = 1;
                    if(($semicolonPos = mb_strpos($language, ';')) !== false) {
                        if(($qPos = mb_strpos($language, '=', $semicolonPos)) !== false) {
                            $quality = min(1, max(floatval(trim(mb_substr($language, $qPos + 1))) ?: 1, 0));
                        }
                        $language = trim(mb_substr($language, 0, $semicolonPos));
                    }
                    $acceptLanguages[mb_strtolower($language)] = $quality;
                }
                arsort($acceptLanguages);

                self :: $acceptLanguages = $acceptLanguages;
            } else {
                self :: $acceptLanguages = array();
            }
            self :: $isGlobalInitialized = true;
        }

        $files = array();

        foreach($_FILES as $rootName => $fileInfo) {

            if(!is_array($fileInfo['name'])) {
                $originalFileName = $fileInfo['name'];
                $mimeType = $fileInfo['type'];
                $fileName = $fileInfo['tmp_name'];
                $status = $fileInfo['error'];

                $fileName = $status === UploadedFile :: STATUS_OK ? $fileName : null;

                $files[$rootName] = new DefaultUploadedFile($originalFileName, $mimeType, $status, $fileName);

                continue;
            }


            $index = 0;
            $stack = array();

            $stack['name'] = array($fileInfo['name']);
            $stack['type'] = array($fileInfo['type']);
            $stack['tmp_name'] = array($fileInfo['tmp_name']);
            $stack['error'] = array($fileInfo['error']);
            $stack['size'] = array($fileInfo['size']);

            reset($stack['name']);
            reset($stack['type']);
            reset($stack['tmp_name']);
            reset($stack['error']);
            reset($stack['size']);

            $keys = array();

            $target = array();

            while(count($stack['name']) > 0) {
                list($key, $name)	= $current	= each($stack['name'][$index]);
                list($_, $type)					= each($stack['type'][$index]);
                list($_, $tmp_name)				= each($stack['tmp_name'][$index]);
                list($_, $error)				= each($stack['error'][$index]);
                list($_, $size)					= each($stack['size'][$index]);

                // The array we're visiting is emptied out, go to the next one
                if($current === false) {

                    $key = array_pop($keys);

                    array_pop($stack['name']);
                    array_pop($stack['type']);
                    array_pop($stack['tmp_name']);
                    array_pop($stack['error']);
                    array_pop($stack['size']);

                    $index--;
                    if($index >= 0) {
                        $target[$index][$key] = array_pop($target);
                    }

                    continue;
                }

                // If we're visiting a new array push it for traversion and store the key
                if(is_array($name)) {
                    reset($name);
                    reset($type);
                    reset($tmp_name);
                    reset($error);
                    reset($size);

                    $index++;

                    $stack['name'][] = $name;
                    $stack['type'][] = $type;
                    $stack['tmp_name'][] = $tmp_name;
                    $stack['error'][] = $error;
                    $stack['size'][] = $size;

                    $keys[] = $key;

                    $target[] = array();

                    continue;
                }

                $tmp_name = $error === UploadedFile :: STATUS_OK ? $tmp_name : null;

                $target[$index][$key] = new DefaultUploadedFile($name, $type, $error, $tmp_name);
            }

            $files[$rootName] = array_pop($target);
        }

        $this->files = $files;

    }

    /**
     * Strip null bytes from  a (nested) array of strings
     *
     * @param array|string $value
     */
    private static function stripNullBytes(&$value) {
        if(is_array($value)) {
            for(reset($value), $i = 0, $c = count($value); $key = key($value), $i < $c; $i++) {
                $nvalue =& $value[$key];

                unset($value[$key]);

                self :: stripNullBytes($key);
                self :: stripNullBytes($nvalue);

                $value[$key] = $nvalue;
            }
        } elseif(is_string($value)) {
            $value = str_replace(chr(0), '', $value);
        }
    }

    /**
     *
     * @param string|array $value
     */
    private static function unescape(&$value) {
        if(is_array($value)) {
            for(reset($value), $i = 0, $c = count($value); $key = key($value), $i < $c; $i++) {
                $nvalue =& $value[$key];
                unset($value[$key]);

                self :: unescape($key);
                self :: unescape($nvalue);

                $value[$key] = $nvalue;
            }
        } elseif(is_string($value)) {
            $value = stripslashes($value);
        }
    }
}

class DefaultUploadedFileSet extends UploadedFileSet {

    private $files = null;

    public function __construct(array $files) {
        $this->files = $files;
    }

    public function getName() {

    }

    public function getFiles() {
        $result = array();

        foreach($this->files as $name => $file) {
            if($file instanceof UploadedFile) {
                $result[$name] = $file;
            }
        }

        return $result;
    }

    public function getFileSets() {
        $result = array();

        foreach($this->files as $name => $file) {
            if(!($file instanceof UploadedFile)) {
                $result[$name] = $file;
            }
        }

        return $result;
    }

    public function getFile($name) {
        // flatten the arguments to a single array of keys
        if(!is_array($name)) {
            $name = array($name);
        }

        $arguments = func_get_args();
        array_shift($arguments);

        while(count($arguments) > 0) {
            $next = array_shift($arguments);

            if(!is_array($next)) {
                $next = array($next);
            }

            $name = array_merge($name, $next);
        }

        $file = $this->files;

        // traverse the keys to find the correct file
        do {
            $key = array_shift($name);

            if(!isset($file[$key])) {
                $file = false;
            }

            $file = $file[$key];
        } while($file !== false && count($name) > 0);

        if($file === false || !($file instanceof UploadedFile)) {
            return null;
        }

        return $file;
    }

    public function getFileSet($name) {
        // flatten the arguments to a single array of keys
        if(!is_array($name)) {
            $name = array($name);
        }

        $arguments = func_get_args();
        array_shift($arguments);

        while(count($arguments) > 0) {
            $next = array_shift($arguments);

            if(!is_array($next)) {
                $next = array($next);
            }

            $name = array_merge($name, $next);
        }

        $file = $this->files;

        // traverse the keys to find the correct fileset
        do {
            $key = array_shift($name);

            if(!isset($file[$key])) {
                $file = false;
            }

            $file = $file[$key];
        } while(is_array($file) && count($name) > 0);

        if($file === false || !is_array($file)) {
            return null;
        }

        return new DefaultUploadedFileSet($file);
    }
}

class DefaultUploadedFile extends UploadedFile {

    /**
     *
     * @var SplFileObject
     */
    private $splFileObject = null;
    private $originalFileName = null;
    private $mimeType = null;
    private $status = null;

    public function __construct($originalFileName, $mimeType, $status, $localFileName = null) {
        $this->originalFileName = $originalFileName;
        $this->mimeType = $mimeType;
        $this->status = $status;

        if($localFileName !== null && is_uploaded_file($localFileName)) {
            $this->splFileObject = new \SplFileObject($localFileName);
        }

    }

    /**
     *
     * @return string
     */
    public function getOriginalFilename() {
        return $this->originalFileName;
    }

    public function getStatus() {
        return $this->status;
    }

    public function rewind() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->rewind();
    }

    public function eof() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->eof();
    }

    public function valid() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->valid();
    }

    public function fgets() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fgets();
    }

    /**
     * @param delimiter[optional]
     * @param enclosure[optional]
     */
    public function fgetcsv($delimiter = null, $enclosure = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fgetcsv($delimiter, $enclosure);
    }

    /**
     * @param delimiter[optional]
     * @param enclosure[optional]
     */
    public function setCsvControl($delimiter = null, $enclosure = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->setCsvControl($delimiter, $enclosure);
    }

    public function getCsvControl() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getCsvControl();
    }

    /**
     * @param operation
     * @param wouldblock[optional]
     */
    public function flock($operation, &$wouldblock = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->flock($operation, $wouldblock);
    }

    public function fflush() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fflush();
    }

    public function ftell() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->ftell();
    }

    /**
     * @param pos
     * @param whence[optional]
     */
    public function fseek($pos, $whence = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fseek($pos, $whence);
    }

    public function fgetc() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fgetc();
    }

    public function fpassthru() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fpassthru();
    }

    /**
     * @param allowable_tags[optional]
     */
    public function fgetss($allowable_tags = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fgetss($allowable_tags);
    }

    /**
     * @param format
     */
    public function fscanf($format) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fscanf($format);
    }

    /**
     * @param str
     * @param length[optional]
     */
    public function fwrite($str, $length = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fwrite($str, $length);
    }

    public function fstat() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->fstat();
    }

    /**
     * @param size
     */
    public function ftruncate($size) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->ftruncate($size);
    }

    public function current() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->current();
    }

    public function key() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->key();
    }

    public function next() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->next();
    }

    /**
     * @param flags
     */
    public function setFlags($flags) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->setFlags($flags);
    }

    public function getFlags() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getFlags();
    }

    /**
     * @param max_len
     */
    public function setMaxLineLen($max_len) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->setMaxLineLen($max_len);
    }

    public function getMaxLineLen() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getMaxLineLen();
    }

    public function hasChildren() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->hasChildren();
    }

    public function getChildren() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getChildren();
    }

    /**
     * @param line_pos
     */
    public function seek($line_pos) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->seek($line_pos);
    }

    public function getCurrentLine() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getCurrentLine();
    }

    public function __toString() {
        if($this->splFileObject === null) {
            return "invalid file";
        }
        return $this->splFileObject->__toString();
    }

    public function getPath() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getPath();
    }

    public function getFilename() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getFilename();
    }

    /**
     * @param suffix[optional]
     */
    public function getBasename($suffix = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getBasename($suffix);
    }

    public function getPathname () {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getPathname ();
    }

    public function getPerms() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getPerms();
    }

    public function getInode() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getInode();
    }

    public function getSize() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getSize();
    }

    public function getOwner() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getOwner();
    }

    public function getGroup() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getGroup();
    }

    public function getATime() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getATime();
    }

    public function getMTime() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getMTime();
    }

    public function getCTime() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getCTime();
    }

    public function getType() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getType();
    }

    public function isWritable() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->isWritable();
    }

    public function isReadable() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->isReadable();
    }

    public function isExecutable() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->isExecutable();
    }

    public function isFile() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->isFile();
    }

    public function isDir() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->isDir();
    }

    public function isLink() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->isLink();
    }

    public function getLinkTarget() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getLinkTarget();
    }

    public function getRealPath() {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getRealPath();
    }

    /**
     * @param class_name[optional]
     */
    public function getFileInfo($class_name = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getFileInfo($class_name);
    }

    /**
     * @param class_name[optional]
     */
    public function getPathInfo($class_name = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->getPathInfo($class_name);
    }

    /**
     * @param open_mode[optional]
     * @param use_include_path[optional]
     * @param context[optional]
     */
    public function openFile($open_mode = null, $use_include_path = null, $context = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->openFile($open_mode, $use_include_path, $context);
    }

    /**
     * @param class_name[optional]
     */
    public function setFileClass($class_name = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->setFileClass($class_name);
    }

    /**
     * @param class_name[optional]
     */
    public function setInfoClass($class_name = null) {
        if($this->splFileObject === null) { throw new Exception("File was not successfully uploaded"); }
        return $this->splFileObject->setInfoClass($class_name);
    }
}

